﻿'Copyright (c) 2008, William Severance, Jr., WESNet Designs
'All rights reserved.

'Redistribution and use in source and binary forms, with or without modification, are permitted
'provided that the following conditions are met:

'Redistributions of source code must retain the above copyright notice, this list of conditions
'and the following disclaimer.

'Redistributions in binary form must reproduce the above copyright notice, this list of conditions
'and the following disclaimer in the documentation and/or other materials provided with the distribution.

'Neither the name of William Severance, Jr. or WESNet Designs may be used to endorse or promote
'products derived from this software without specific prior written permission.

'Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
'            OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
'            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE
'            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
'            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
'            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
'            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
'            IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'Although I will try to answer questions regarding the installation and use of this software when
'such questions are submitted via e-mail to the below address, no promise of further
'support or enhancement is made nor should be assumed.

'Developer Contact Information:
'     William Severance, Jr.
'     WESNet Designs
'     679 Upper Ridge Road
'     Bridgton, ME 04009
'     Phone: 207-647-9375
'     E-Mail: bill@wesnetdesigns.com
'     Website: www.wesnetdesigns.com

Imports System.IO
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Text.RegularExpressions

Imports WESNet.Utilities

Imports DotNetNuke
Imports DotNetNuke.Services
Imports DotNetNuke.Services.Localization
Imports DotNetNuke.Common.Globals
Imports DotNetNuke.Common.Utilities

Namespace WESNet.DNN.Controls.ImageEditorControl

    Public Class Utilities

        Public Enum DuplicateActions As Integer
            Replace = 0
            MakeUnique = 1
            Abort = 2
            ThrowException = 3
        End Enum

        Public Shared Function InsertFileSuffix(ByVal FilePath As String, ByVal FileSuffix As String) As String
            Dim s1, s2 As String
            Dim N As Integer = FilePath.Replace(glbProtectedExtension, "").LastIndexOf(".")
            If N < 0 Then
                s1 = FilePath
                s2 = String.Empty
            Else
                s1 = FilePath.Substring(0, N)
                s2 = FilePath.Substring(N)
            End If
            If s1.EndsWith(FileSuffix) Then
                Return FilePath
            Else
                Return s1 & FileSuffix & s2
            End If
        End Function

        Public Shared Function AddDefaultExtension(ByVal FilePath As String, ByVal DefaultExtension As String) As String
            If String.IsNullOrEmpty(Path.GetExtension(FilePath)) Then
                Return FilePath & "." & DefaultExtension.TrimStart("."c)
            Else
                Return FilePath
            End If
        End Function

        Public Shared Function GetFilePathWithoutExtension(ByVal FilePath As String) As String
            Dim N As Integer = FilePath.Replace(glbProtectedExtension, "").LastIndexOf(".")
            If N < 0 Then
                Return FilePath
            Else
                Return FilePath.Substring(0, N)
            End If
        End Function

        'Note: This method was copied from DotNetNuke.Services.FileSystemUtils as the scope of the
        'original method was declared Private.

        Public Shared Sub WriteStream(ByVal fileId As Integer, ByVal inStream As Stream, ByVal fileName As String, ByVal storageLocation As Integer, ByVal closeInputStream As Boolean)

            Dim objFileController As New DotNetNuke.Services.FileSystem.FileController

            ' Buffer to read 2K bytes in chunk:
            Dim arrData(2048) As Byte
            Dim outStream As Stream = Nothing
            Select Case storageLocation
                Case FileSystem.FolderController.StorageLocationTypes.DatabaseSecure
                    objFileController.ClearFileContent(fileId)
                    outStream = New MemoryStream
                Case FileSystem.FolderController.StorageLocationTypes.SecureFileSystem
                    If File.Exists(fileName & glbProtectedExtension) = True Then
                        File.Delete(fileName & glbProtectedExtension)
                    End If
                    outStream = New FileStream(fileName & glbProtectedExtension, FileMode.Create)
                Case FileSystem.FolderController.StorageLocationTypes.InsecureFileSystem
                    If File.Exists(fileName) = True Then
                        File.Delete(fileName)
                    End If
                    outStream = New FileStream(fileName, FileMode.Create)
            End Select

            Try
                ' Total bytes to read:
                Dim intLength As Integer
                ' Read the data in buffer
                intLength = inStream.Read(arrData, 0, arrData.Length)
                While intLength > 0
                    ' Write the data to the current output stream.
                    outStream.Write(arrData, 0, intLength)

                    'Read the next chunk
                    intLength = inStream.Read(arrData, 0, arrData.Length)
                End While

                If storageLocation = FileSystem.FolderController.StorageLocationTypes.DatabaseSecure Then
                    outStream.Seek(0, SeekOrigin.Begin)
                    objFileController.UpdateFileContent(fileId, outStream)
                End If
            Catch ex As Exception
            Finally
                If IsNothing(inStream) = False And closeInputStream Then
                    ' Close the file.
                    inStream.Close()
                End If
                If IsNothing(outStream) = False Then
                    ' Close the file.
                    outStream.Close()
                End If
            End Try
        End Sub


        'Saves the image bitmap to the Filepath with the specified jpg quality
        'If AllowReplace is true, replaces file of same name without error
        'Returns DNN FileID if successful, otherwise returns negative integer representing following errors:
        '-1 Image object is null
        '-2 Filename is null or empty
        '-3 Unable to create memory stream from image data
        '-4 Image data is null
        '-5 Insufficient portal filespace in which to save image data
        '-6 Folder not found in DNN Folders database
        '-7 File already exists and HandleDuplicate=DuplicateActions.Abort
        '-8 File already exists and could not be replaced or made unique
        '-9 File could not be added to DNN Files database
        '-10 Memory stream could not be written to storage location
        '-11 Unknown error

        Public Overloads Shared Function SaveImage(ByVal src As Bitmap, ByVal ParentPath As String, ByRef Filepath As String, _
                                                   ByVal FileSuffix As String, ByVal HandleDuplicate As DuplicateActions, _
                                                   ByVal Quality As Long, ByVal UseOctreeQuantizer As Boolean, _
                                                   ByVal Portal As Entities.Portals.PortalInfo) As Integer

            If src Is Nothing Then Return -1

            Dim Folder As String = FileSystemUtils.FormatFolderPath(Path.GetDirectoryName(Filepath).Replace("\", "/"))
            Dim Filename As String = InsertFileSuffix(Path.GetFileName(Filepath), FileSuffix)
            Dim Ext As String = Path.GetExtension(Filename).ToLower.TrimStart("."c)
            If String.IsNullOrEmpty(Filename) Then Return -2

            Dim fs As New MemoryStream
            Try
                Select Case Ext
                    Case "jpg", "jpeg"
                        ImageUtilities.SaveJPEG(src, Quality, fs)
                    Case "gif"
                        ImageUtilities.SaveGIF(src, UseOctreeQuantizer, fs)
                    Case Else
                        Dim imgFormat As ImageFormat = ImageUtilities.GetImageFormat(Ext)
                        src.Save(fs, imgFormat)
                End Select
            Catch ex As Exception
                If Not fs Is Nothing Then fs.Dispose()
                Return -3
            End Try

            Dim FileID As Integer
            Try
                Dim FileSize As Long = fs.Length
                If FileSize = 0 Then
                    FileID = -4
                ElseIf Not IsSpaceAvailable(FileSize, Portal) Then
                    FileID = -5
                Else
                    Dim fc As New Services.FileSystem.FileController
                    Dim fldc As New Services.FileSystem.FolderController
                    Dim fldi As FileSystem.FolderInfo
                    fldi = fldc.GetFolder(Portal.PortalID, Folder, False)
                    If fldi Is Nothing Then
                        FileID = -6
                    Else
                        Dim Fullpath As String = Path.Combine(ParentPath & Folder, Filename).Replace("/", "\")
                        If Not fc.GetFile(Filename, Portal.PortalID, fldi.FolderID) Is Nothing Then
                            Select Case HandleDuplicate
                                Case DuplicateActions.Abort
                                    FileID = -7
                                Case DuplicateActions.Replace
                                    Try
                                        DeleteFile(Fullpath, Portal.PortalID, True)
                                    Catch ex As Exception
                                        FileID = -8
                                    End Try
                                Case DuplicateActions.MakeUnique
                                    Filename = GetUniqueFilename(Portal.PortalID, fldi.FolderID, Filename, FileSuffix)
                                    If Filename Is Nothing Then
                                        FileID = -8
                                    Else
                                        Filepath = Path.Combine(Folder, Filename).Replace("\", "/")
                                        Fullpath = Path.Combine(ParentPath & Folder, Filename).Replace("/", "\")
                                    End If
                                Case DuplicateActions.ThrowException
                                    Throw New IOException("Save image failed because " & Filepath & " already exists.")
                            End Select
                        End If
                        If FileID >= 0 Then
                            Dim fi As New FileSystem.FileInfo
                            With fi
                                .PortalId = Portal.PortalID
                                .Folder = fldi.FolderPath
                                .StorageLocation = fldi.StorageLocation
                                .FolderId = fldi.FolderID
                                .FileName = Filename
                                .Extension = Path.GetExtension(Filename).TrimStart("."c).ToLower()
                                .ContentType = ImageUtilities.GetContentType(src.RawFormat)
                                If .ContentType = String.Empty Then .ContentType = ImageUtilities.GetContentType(.Extension)
                                .Width = src.Width
                                .Height = src.Height
                            End With
                            Try
                                FileID = fc.AddFile(fi)
                            Catch ex As Exception
                                FileID = -9
                            End Try
                            If FileID >= 0 Then
                                Try
                                    fs.Seek(0, SeekOrigin.Begin)
                                    WriteStream(fi.FileId, fs, Fullpath, fi.StorageLocation, True)
                                    'Update the FileSize in the database
                                    fc.UpdateFile(FileID, fi.FileName, fi.Extension, FileSize, fi.Width, fi.Height, fi.ContentType, fi.Folder, fi.FolderId)
                                Catch ex As Exception
                                    fc.DeleteFile(Portal.PortalID, Filename, fldi.FolderID, False)
                                    FileID = -10
                                End Try
                            End If
                        End If
                    End If
                End If
            Catch ex As Exception
                FileID = -11
            Finally
                If Not fs Is Nothing Then fs.Dispose()
            End Try
            Return FileID
        End Function

        Public Shared Function IsSpaceAvailable(ByVal ContentLength As Long, ByVal Portal As Entities.Portals.PortalInfo) As Boolean
            Dim pc As New Entities.Portals.PortalController
            Return ((((pc.GetPortalSpaceUsedBytes(Portal.PortalID) + ContentLength) / 1000000) <= Portal.HostSpace) _
                       Or Portal.HostSpace = 0) Or (Entities.Portals.PortalController.GetCurrentPortalSettings().ActiveTab.ParentId = Portal.SuperTabId)
        End Function

        'Checks the supplied full Filepath to determine if the filename (with the FileSuffix inserted before the extension)
        'exists in the directory. If it does, appends 001 ... 999 between the filename and FileSuffix until the modified
        'filename is unique in the directory. If it cannot create a unique name in this manner, returns Nothing
        '
        Public Shared Function GetUniqueFilepath(ByVal Filepath As String, ByVal FileSuffix As String) As String
            Dim N As Integer
            Dim s1 As String = GetFilePathWithoutExtension(Filepath)
            Dim s2 As String = Filepath.Replace(s1, "")
            Dim s3 As String
            Dim rx As New Regex("(.*?)(\d*)(?:" & FileSuffix & "|)$")
            Dim m As Match = rx.Match(s1)
            s1 = m.Groups(1).Value
            If m.Groups(2).Value <> String.Empty Then
                N = Integer.Parse(m.Groups(2).Value)
            Else
                N = -1
            End If
            Do
                N += 1
                s3 = s1 & N.ToString("000;;''") & FileSuffix & s2
            Loop Until Not File.Exists(s3) Or N = 999
            If N = 999 Then
                Return Nothing
            Else
                Return s3
            End If
        End Function

        Public Shared Function GetUniqueFilename(ByVal PortalID As Integer, ByVal FolderID As Integer, ByVal Filename As String, ByVal FileSuffix As String) As String
            Dim fc As New FileSystem.FileController
            Dim Files As New Generic.List(Of String)
            Dim dr As IDataReader = fc.GetFiles(PortalID, FolderID)
            Try
                While dr.Read
                    Files.Add(CType(dr("Filename"), String))
                End While
            Catch
                '
            Finally
                If Not dr Is Nothing Then dr.Close()
            End Try
            If Files.Count = 0 Then
                Return Filename
            Else
                Dim N As Integer
                Dim s1 As String = GetFilePathWithoutExtension(Filename)
                Dim s2 As String = Filename.Replace(s1, "")
                Dim s3 As String
                Dim rx = New Regex("(.*?)(\d*)(?:" & FileSuffix & "|)$")
                Dim m As Match = rx.Match(s1)
                s1 = m.Groups(1).Value
                If m.Groups(2).Value <> String.Empty Then
                    N = Integer.Parse(m.Groups(2).Value)
                Else
                    N = -1
                End If
                Do
                    N += 1
                    s3 = s1 & N.ToString("000;;''") & s2
                Loop Until Not Files.Contains(s3) Or N = 999
                If N = 999 Then
                    Return Nothing
                Else
                    Return s3
                End If
            End If
        End Function

        Public Shared Sub DeletePhysicalFile(ByVal Fullpath As String)
            If File.Exists(Fullpath) Then
                File.SetAttributes(Fullpath, FileAttributes.Normal)
                File.Delete(Fullpath)
            End If
        End Sub

        Public Shared Function DeleteFile(ByVal Fullpath As String, ByVal PortalID As Integer, ByVal ClearCache As Boolean) As String
            Dim msg As String = ""

            Try
                DeletePhysicalFile(Fullpath) 'Try to delete an insecure storage system file
                DeletePhysicalFile(Fullpath & DotNetNuke.Common.glbProtectedExtension) 'Try to delete secure storage system file

                Dim Folder As String = Common.Globals.GetSubFolderPath(Fullpath)
                Dim Filename As String = Path.GetFileName(Fullpath.Replace(DotNetNuke.Common.glbProtectedExtension, ""))

                'Remove file from DataBase
                Dim fc As New FileSystem.FileController
                Dim fldc As New FileSystem.FolderController
                Dim fldi As FileSystem.FolderInfo = fldc.GetFolder(PortalID, Folder, False)
                fc.DeleteFile(PortalID, Filename, fldi.FolderID, ClearCache)
            Catch ex As Exception
                msg = ex.Message
            End Try
            Return msg
        End Function

        Public Shared Function DeleteFile(ByVal FileID As Integer, ByVal ParentPath As String, ByVal PortalID As Integer, ByVal ClearCache As Boolean) As String
            Dim msg As String = ""
            Try
                Dim Fullpath As String
                Dim fc As New FileSystem.FileController
                Dim fi As FileSystem.FileInfo = fc.GetFileById(FileID, PortalID)
                Fullpath = (ParentPath & fi.Folder & fi.FileName).Replace("/", "\")

                DeletePhysicalFile(Fullpath) 'Try to delete an insecure storage system file
                DeletePhysicalFile(Fullpath & DotNetNuke.Common.glbProtectedExtension) 'Try to delete secure storage system file

                fc.DeleteFile(PortalID, fi.FileName, fi.FolderId, ClearCache)

            Catch ex As Exception
                msg = ex.Message
            End Try
            Return msg
        End Function
    End Class
End Namespace
